package com.wei.czz.framework.common.service;

import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.property.SnowflakeProperty;
import lombok.AllArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.LinkedBlockingDeque;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wyw
 * date: 2024-11-24 15:27
 * className: SnowflakeService 雪花主键生成类
 * version: 1.1
 * description:
 */
@Component
@AllArgsConstructor
public class SnowflakeService {

    private static final Logger log = LoggerFactory.getLogger(SnowflakeService.class);

    /**
     * 雪花主键生成对象存储阻塞队列
     */
    private static final BlockingDeque<SnowFlake> deque = new LinkedBlockingDeque<>();

    /**
     * 锁对象
     */
    private static final Object lock = new Object();

    /**
     * 雪花主键配置对象
     */
    private final SnowflakeProperty snowflakeProperty;

    @PostConstruct
    public void init() {
        /*
            初始化阻塞队列
         */
        Integer dataCount = snowflakeProperty.getDataCount();
        Integer workCount = snowflakeProperty.getWorkCount();
        log.info("【雪花主键】阻塞队列初始化。dataCount={} workCount={}", dataCount, workCount);
        for (int i = 0; i < dataCount; i++) {
            for (int j = 0; j < workCount; j++) {
                deque.addLast(new SnowFlake(i, j));
            }
        }
        log.info("【雪花主键】阻塞队列初始化完成");
    }

    /**
     * 获取一个雪花主键
     * @return 雪花主键
     */
    public Long get() {
        SnowFlake snowFlake = this.getSnowFlake();
        try {
            return snowFlake.generate();
        } catch (Exception e) {
            log.info("雪花主键生成异常。message={}", e.getMessage(), e);
            throw new CzzException();
        } finally {
            this.addDeque(snowFlake);
        }
    }

    /**
     * 批量获取雪花主键
     * @param size 获取个数
     * @return 雪花主键列表
     */
    public List<Long> get(int size) {
        SnowFlake snowFlake = this.getSnowFlake();
        try {
            List<Long> idList = new ArrayList<>(size);
            for (int i = 0; i < size; i++) {
                // 生成一个主键值
                idList.add(snowFlake.generate());
            }
            return idList;
        } catch (Exception e) {
            log.info("雪花主键批量生成异常。message={}", e.getMessage(), e);
            throw new CzzException();
        } finally {
            this.addDeque(snowFlake);
        }
    }

    /**
     * 获取一个字符串格式雪花主键
     * @return 雪花主键
     */
    public String find() {
        return String.valueOf(get());
    }

    /**
     * 批量获取雪花主键
     * @param size 获取个数
     * @return 雪花主键列表
     */
    public List<String> find(int size) {
        SnowFlake snowFlake = this.getSnowFlake();
        try {
            List<String> idList = new ArrayList<>(size);
            for (int i = 0; i < size; i++) {
                // 生成一个主键值
                idList.add(String.valueOf(snowFlake.generate()));
            }
            return idList;
        } catch (Exception e) {
            log.info("雪花主键批量生成异常。message={}", e.getMessage(), e);
            throw new CzzException();
        } finally {
            this.addDeque(snowFlake);
        }
    }

    /**
     * 从阻塞队列中获取雪花主键生成对象
     * @return 雪花主键生成对象
     */
    private SnowFlake getSnowFlake() {
        while (true) {
            try {
                // 从阻塞队列中获取雪花主键生成对象
                SnowFlake snowFlake = deque.pollFirst();
                if (Objects.isNull(snowFlake)) {
                    // 未获取到，线程挂起，等待队列有数据再唤醒线程
                    synchronized (lock) {
                        lock.wait();
                    }
                } else {
                    return snowFlake;
                }
            } catch (Exception e) {
                log.error("从阻塞队列中获取雪花主键生成对象异常。message={}", e.getMessage(), e);
                throw new CzzException();
            }
        }
    }

    /**
     * 雪花主键生成对象重新加入队列
     * @param snowFlake 雪花主键生成对象
     */
    private void addDeque(SnowFlake snowFlake) {
        // 雪花主键生成对象重新加入队列
        deque.addLast(snowFlake);
        synchronized (lock) {
            // 随机唤醒一个挂起的线程
            lock.notify();
        }
    }

    /**
     * 雪花主键生成类
     */
    private static class SnowFlake {

        /**
         * 数据中心id（0-31之间的数，左闭右闭区间）
         */
        private final long dataId;

        /**
         * 工作机器id（0-31之间的数，左闭右闭区间）
         */
        private final long workId;

        /**
         * 时间部分所占长度
         */
        private static final int TIME_LEN = 41;

        /**
         * 数据中心id所占长度
         */
        private static final int DATA_LEN = 5;

        /**
         * 机器id所占长度
         */
        private static final int WORK_LEN = 5;

        /**
         * 毫秒内序列所占长度
         */
        private static final int SEQ_LEN = 12;

        /**
         * 定义起始时间 2022-01-01 00:00:00:000
         */
        private static final long START_TIME = 1640966400000L;

        /**
         * 上次生成ID的时间截
         */
        private static long LAST_TIME_STAMP = -1L;

        /**
         * 时间部分向左移动的位数（TIME_LEFT_BIT = 22）
         */
        private static final int TIME_LEFT_BIT = 64 - 1 - TIME_LEN;

        /**
         * 数据中心id左移位数（DATA_LEFT_BIT = 17）
         */
        private static final int DATA_LEFT_BIT = TIME_LEFT_BIT - DATA_LEN;

        /**
         * 机器id左移位数（WORK_LEFT_BIT = 12）
         */
        private static final int WORK_LEFT_BIT = DATA_LEFT_BIT - WORK_LEN;

        /**
         * 上一次的毫秒内序列值
         */
        private static long LAST_SEQ = 0L;

        /**
         * 一毫秒内可生成的雪花主键个数（SEQ_MAX_NUM = 4095）
         */
        private static final long SEQ_MAX_NUM = ~(-1 << SEQ_LEN);

        public SnowFlake(long dataId, long workId) {
            this.dataId = dataId;
            this.workId = workId;
        }

        /**
         * 生成一个雪花主键值
         * @return 雪花主键值
         */
        public long generate(){
            long now = System.currentTimeMillis();

            //如果当前时间小于上一次ID生成的时间戳，说明系统时钟回退过这个时候应当抛出异常
            if (now < LAST_TIME_STAMP) {
                log.error("发生时间回拨，雪花主键生成异常，请务必及时处理。now={} last={}", now, LAST_TIME_STAMP);
                throw new CzzException();
            }

            if (now == LAST_TIME_STAMP) {
                LAST_SEQ = (LAST_SEQ + 1) & SEQ_MAX_NUM;
                if (LAST_SEQ == 0){
                    now = nextMillis(LAST_TIME_STAMP);
                }
            } else {
                LAST_SEQ = 0;
            }

            // 上次生成ID的时间截
            LAST_TIME_STAMP = now;

            // 生成
            return ((now - START_TIME) << TIME_LEFT_BIT) | (dataId << DATA_LEFT_BIT) | (workId << WORK_LEFT_BIT) | LAST_SEQ;
        }

        /**
         * 获取下一不同毫秒的时间戳，不能与最后的时间戳一样
         */
        private long nextMillis(long lastMillis) {
            long now = System.currentTimeMillis();
            while (now <= lastMillis) {
                now = System.currentTimeMillis();
            }
            return now;
        }

    }
}
